/*******************************************************************************
* 
* A simple Logger_ class
* 
* 
******************************************************************************/
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_NON_CONFORMING_SWPRINTFS
#include "logger.h"
#include "process.h"
#include "strings.h"

#include <sstream>
#include <stdarg.h>
#include <ctime>

using namespace std;
using namespace noria::text;

namespace noria{ namespace diagnostic
{
    inline wstring getLoggerName()
    {
        wstringstream log;

        log << Process::getBaseName() << "__";

        time_t now = time(0);
        tm localtm;
        localtime_s(&localtm, &now);

        // http://en.cppreference.com/w/c/chrono/strftime
        char timeBuff[USHRT_MAX];
        strftime(timeBuff, USHRT_MAX, "%Y%m%d-%H%M%S", &localtm);
        log << timeBuff << "__";

        log << Process::getpid() << ".log";

        return log.str();
    }

    Logger_::Logger_(void)
    {
        // initialize the enumerator map
        m_map.insert(make_pair(LogLevel::DEBUG_, "DEBUG"));
        m_map.insert(make_pair(LogLevel::INFO, "INFO"));
        m_map.insert(make_pair(LogLevel::WARN, "WARN"));
        m_map.insert(make_pair(LogLevel::ERROR_, "ERROR"));
        m_map.insert(make_pair(LogLevel::FATAL, "FATAL"));

        // create file logger with the pattern '<proc_name>__<date_time>__<proc_id>.log'
        auto log = getLoggerName();
        m_fs.open(log.c_str());

        // write UTF-8 BOM (only for Windows) 0xEF,0xBB,0xBF
        char bom[] = {0xEF,0xBB,0xBF};
        m_fs.write(bom, 3);

        // write header
        write(LogLevel::INFO, __FILE__, __LINE__, "Logger::ctor() Logger was created.");
    }


    Logger_::~Logger_(void)
    {
        // write footer
        write(LogLevel::INFO, __FILE__, __LINE__, "Logger::~ctor() Logger was closed.");

        // close the file logger
        if(m_fs.is_open() && !m_fs.bad())
            m_fs.close();
    }

    unique_ptr<Logger_> Logger_::s_instance;
    // TODO: it is not thread safty, 
    // NOTE: *DONOT* delete this pointer
    Logger_* Logger_::instance()
    {
        if(!s_instance)
        {
            s_instance.reset(new Logger_());
        }
        return s_instance.get();
    }

    void Logger_::write(LogLevel level, const char* source, int line, const char* msg, ...)
    {
        stringstream buff;
        {
            // Current date/time based on current system
            time_t now = time(0);

            // Convert now to tm struct for local timezone
            tm localtm;
            localtime_s(&localtm, &now);

            buff << "[" << asctime(&localtm);
            long pos = buff.tellp();
            buff.seekp( pos - 1 ); // seems like a bug, have to go back 1 byte to remove \n
            buff << "] ";

            buff << source << "#" << line;

            buff << " [" << m_map[level] << "] ";

            // process variadic parameters
            {
                char msgFmt[UCHAR_MAX];
                va_list ap;
                va_start(ap, msg); //Requires the last fixed parameter (to get the address)
                vsprintf(msgFmt, msg, ap);
                va_end(ap);

                buff << msgFmt << endl;
            }		

            buff.flush();
        }

        // flush to file logger
        m_fs << buff.str();
        m_fs.flush();

        // flush to console logger
        cout << buff.str();
        cout.flush();
    }

    void Logger_::write(LogLevel level, const char* source, int line, const wchar_t* msg, ...)
    {
        wstringstream buff;
        {
            // Current date/time based on current system
            time_t now = time(0);

            // Convert now to tm struct for local timezone
            tm localtm;
            localtime_s(&localtm, &now);

            buff << "[" << asctime(&localtm);
            long pos = buff.tellp();
            buff.seekp( pos - 1 ); // seems like a bug, have to go back 1 byte to remove \n
            buff << "] ";

            buff << source << "#" << line;

            buff << " [" << m_map[level].c_str() << "] ";

            // process variadic parameters
            {
                wchar_t msgFmt[UCHAR_MAX];
                va_list ap;
                va_start(ap, msg); //Requires the last fixed parameter (to get the address)
                vswprintf(msgFmt, msg, ap);
                va_end(ap);

                buff << msgFmt << endl;
            }		

            buff.flush();
        }

        // get the utf-8 string
        auto ansi = Strings::w2a(buff.str());

        // flush to file logger
        m_fs << ansi;
        m_fs.flush();

        // flush to console logger
        cout << ansi;
        cout.flush();
    }
}}